// Copyright 2006 The Android Open Source Project

package android;

import javatest.JacocoFilters;

import java.lang.reflect.*;
import java.io.IOException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Reflection test.
 */
public class AndroidTest8019 {
	private static boolean FULL_ACCESS_CHECKS = false;  // b/5861201
	public AndroidTest8019() {}
	public AndroidTest8019(ArrayList<Integer> stuff) {}

	void printMethodInfo(Method meth) {
		Class[] params, exceptions;
		int i;

		System.out.println("Method name is " + meth.getName());
		System.out.println(" Declaring class is "
			+ meth.getDeclaringClass().getName());
		params = meth.getParameterTypes();
		for (i = 0; i < params.length; i++)
			System.out.println(" Arg " + i + ": " + params[i].getName());
		exceptions = meth.getExceptionTypes();
		for (i = 0; i < exceptions.length; i++)
			System.out.println(" Exc " + i + ": " + exceptions[i].getName());
		System.out.println(" Return type is " + meth.getReturnType().getName());
		System.out.println(" Access flags are 0x"
			+ Integer.toHexString(meth.getModifiers()));
		//System.out.println(" GenericStr is " + meth.toGenericString());
	}

	void printFieldInfo(Field field) {
		System.out.println("Field name is " + field.getName());
		System.out.println(" Declaring class is "
			+ field.getDeclaringClass().getName());
		System.out.println(" Field type is " + field.getType().getName());
		System.out.println(" Access flags are 0x"
			+ Integer.toHexString(field.getModifiers()));
	}

	private void showStrings(Target instance)
		throws NoSuchFieldException, IllegalAccessException {

		Class target = Target.class;
		String one, two, three, four;
		Field field = null;

		field = target.getField("string1");
		one = (String) field.get(instance);

		field = target.getField("string2");
		two = (String) field.get(instance);

		field = target.getField("string3");
		three = (String) field.get(instance);

		System.out.println("  ::: " + one + ":" + two + ":" + three);
	}

	public static void checkAccess() {
		try {
			Class target = android.otherpackage.Other.class;
			Object instance = new android.otherpackage.Other();
			Method meth;

			meth = target.getMethod("publicMethod", (Class[]) null);
			meth.invoke(instance);

			try {
				meth = target.getMethod("packageMethod", (Class[]) null);
				System.err.println("succeeded on package-scope method");
			} catch (NoSuchMethodException nsme) {
				// good
			}


			instance = android.otherpackage.Other.getInnerClassInstance();
			target = instance.getClass();
			meth = target.getMethod("innerMethod", (Class[]) null);
			try {
				if (!FULL_ACCESS_CHECKS) { throw new IllegalAccessException(); }
				meth.invoke(instance);
				System.err.println("inner-method invoke unexpectedly worked");
			} catch (IllegalAccessException iae) {
				// good
			}

			Field field = target.getField("innerField");
			try {
				int x = field.getInt(instance);
				if (!FULL_ACCESS_CHECKS) { throw new IllegalAccessException(); }
				System.err.println("field get unexpectedly worked: " + x);
			} catch (IllegalAccessException iae) {
				// good
			}
		} catch (Exception ex) {
			System.out.println("----- unexpected exception -----");
			ex.printStackTrace();
		}
	}

	public void run() {
		Class target = Target.class;
		Method meth = null;
		Field field = null;
		boolean excep;

		try {
			meth = target.getMethod("myMethod", new Class[] { int.class });

			if (meth.getDeclaringClass() != target)
				throw new RuntimeException();
			printMethodInfo(meth);

			meth = target.getMethod("myMethod", new Class[] { float.class });
			printMethodInfo(meth);

			meth = target.getMethod("myNoargMethod", (Class[]) null);
			printMethodInfo(meth);

			meth = target.getMethod("myMethod",
				new Class[] { String[].class, float.class, char.class });
			printMethodInfo(meth);

			Target instance = new Target();
			Object[] argList = new Object[] {
				new String[] { "hi there" },
				new Float(3.1415926f),
				new Character('Q')
			};
			System.out.println("Before, float is "
				+ ((Float)argList[1]).floatValue());

			Integer boxval;
			boxval = (Integer) meth.invoke(instance, argList);
			System.out.println("Result of invoke: " + boxval.intValue());

			System.out.println("Calling no-arg void-return method");
			meth = target.getMethod("myNoargMethod", (Class[]) null);
			meth.invoke(instance, (Object[]) null);

            /* try invoking a method that throws an exception */
			meth = target.getMethod("throwingMethod", (Class[]) null);
			try {
				meth.invoke(instance, (Object[]) null);
				System.out.println("GLITCH: didn't throw");
			} catch (InvocationTargetException ite) {
				System.out.println("Invoke got expected exception:");
				System.out.println(ite.getClass().getName());
				System.out.println(ite.getCause());
			}
			catch (Exception ex) {
				System.out.println("GLITCH: invoke got wrong exception:");
				ex.printStackTrace();
			}
			System.out.println("");


			field = target.getField("string1");
			if (field.getDeclaringClass() != target)
				throw new RuntimeException();
			printFieldInfo(field);
			String strVal = (String) field.get(instance);
			System.out.println("  string1 value is '" + strVal + "'");

			showStrings(instance);

			field.set(instance, new String("a new string"));
			strVal = (String) field.get(instance);
			System.out.println("  string1 value is now '" + strVal + "'");

			showStrings(instance);

			try {
				field.set(instance, new Object());
				System.out.println("WARNING: able to store Object into String");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected illegal obj store exc");
			}


			try {
				String four;
				field = target.getField("string4");
				four = (String) field.get(instance);
				System.out.println("WARNING: able to access string4: "
					+ four);
			}
			catch (IllegalAccessException iae) {
				System.out.println("  got expected access exc");
			}
			catch (NoSuchFieldException nsfe) {
				System.out.println("  got the other expected access exc");
			}
			try {
				String three;
				field = target.getField("string3");
				three = (String) field.get(this);
				System.out.println("WARNING: able to get string3 in wrong obj: "
					+ three);
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected arg exc");
			}

            /*
             * Try setting a field to null.
             */
			String four;
			field = target.getDeclaredField("string3");
			field.set(instance, null);

            /*
             * Do some stuff with long.
             */
			long longVal;
			field = target.getField("pubLong");
			longVal = field.getLong(instance);
			System.out.println("pubLong initial value is " +
				Long.toHexString(longVal));
			field.setLong(instance, 0x9988776655443322L);
			longVal = field.getLong(instance);
			System.out.println("pubLong new value is " +
				Long.toHexString(longVal));


			field = target.getField("superInt");
			if (field.getDeclaringClass() == target)
				throw new RuntimeException();
			printFieldInfo(field);
			int intVal = field.getInt(instance);
			System.out.println("  superInt value is " + intVal);
			Integer boxedIntVal = (Integer) field.get(instance);
			System.out.println("  superInt boxed is " + boxedIntVal);

			field.set(instance, new Integer(20202));
			intVal = field.getInt(instance);
			System.out.println("  superInt value is now " + intVal);
			field.setShort(instance, (short)30303);
			intVal = field.getInt(instance);
			System.out.println("  superInt value (from short) is now " +intVal);
			field.setInt(instance, 40404);
			intVal = field.getInt(instance);
			System.out.println("  superInt value is now " + intVal);
			try {
				field.set(instance, new Long(123));
				System.out.println("FAIL: expected exception not thrown");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected long->int failure");
			}
			try {
				field.setLong(instance, 123);
				System.out.println("FAIL: expected exception not thrown");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected long->int failure");
			}
			try {
				field.set(instance, new String("abc"));
				System.out.println("FAIL: expected exception not thrown");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected string->int failure");
			}

			try {
				field.getShort(instance);
				System.out.println("FAIL: expected exception not thrown");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected int->short failure");
			}

			field = target.getField("superClassInt");
			printFieldInfo(field);
			int superClassIntVal = field.getInt(instance);
			System.out.println("  superClassInt value is " + superClassIntVal);

			field = target.getField("staticDouble");
			printFieldInfo(field);
			double staticDoubleVal = field.getDouble(null);
			System.out.println("  staticDoubleVal value is " + staticDoubleVal);

			try {
				field.getLong(instance);
				System.out.println("FAIL: expected exception not thrown");
			}
			catch (IllegalArgumentException iae) {
				System.out.println("  got expected double->long failure");
			}

			excep = false;
			try {
				field = target.getField("aPrivateInt");
				printFieldInfo(field);
			}
			catch (NoSuchFieldException nsfe) {
				System.out.println("as expected: aPrivateInt not found");
				excep = true;
			}
			if (!excep)
				System.out.println("BUG: got aPrivateInt");


			field = target.getField("constantString");
			printFieldInfo(field);
			String val = (String) field.get(instance);
			System.out.println("  Constant test value is " + val);


			field = target.getField("cantTouchThis");
			printFieldInfo(field);
			intVal = field.getInt(instance);
			System.out.println("  cantTouchThis is " + intVal);
			try {
				field.setInt(instance, 99);
				System.out.println("ERROR: set-final succeeded");
			} catch (IllegalAccessException iae) {
				System.out.println("  got expected set-final failure");
			}
			intVal = field.getInt(instance);
			System.out.println("  cantTouchThis is now " + intVal);

			field.setAccessible(true);
			field.setInt(instance, 87);     // exercise int version
			field.set(instance, 88);        // exercise Object version
			intVal = field.getInt(instance);
			System.out.println("  cantTouchThis is now " + intVal);

			Constructor<Target> cons;
			Target targ;
			Object[] args;

			cons = target.getConstructor(new Class[] { int.class,float.class });
			args = new Object[] { new Integer(7), new Float(3.3333) };
			System.out.println("cons modifiers=" + cons.getModifiers());
			targ = cons.newInstance(args);
			targ.myMethod(17);

		} catch (Exception ex) {
			System.out.println("----- unexpected exception -----");
			ex.printStackTrace();
		}

		System.out.println("ReflectTest done!");
	}

	static <E> E checkType(E obj, Class<? extends E> type) {
		if (obj != null && !type.isInstance(obj)) {
			throw new ClassCastException("Attempt to insert element of type " + obj.getClass() +
				" into collection of type " + type);
		}
		return obj;
	}

	public static void checkType() {
		Method m;

		try {
			m = AndroidTest8019.class.getDeclaredMethod("checkType",
				Object.class, Class.class);
		} catch (NoSuchMethodException nsme) {
			nsme.printStackTrace();
			return;
		}

		m.setAccessible(true);
		try {
			m.invoke(null, new Object(), Object.class);
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
			return;
		} catch (InvocationTargetException ite) {
			ite.printStackTrace();
			return;
		}

		try {
			System.out.println("checkType invoking null");
			m.invoke(null, new Object(), int.class);
			System.out.println("ERROR: should throw InvocationTargetException");
		} catch (InvocationTargetException ite) {
			System.out.println("checkType got expected exception");
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
			return;
		}
	}

	public static void checkInit() {
		Class niuClass = NoisyInitUser.class;
		Method[] methods;

		methods = JacocoFilters.filter(niuClass.getDeclaredMethods());
		System.out.println("got methods");
        /* neither NoisyInit nor NoisyInitUser should be initialized yet */
		NoisyInitUser niu = new NoisyInitUser();
		NoisyInit ni = new NoisyInit();

		System.out.println("");
	}


	/*
     * Test some generic type stuff.
     */
	public List<String> dummy;
	public Map<Integer,String> fancyMethod(ArrayList<String> blah) { return null; }
	public static void checkGeneric() {
		Field field;
		try {
			field = AndroidTest8019.class.getField("dummy");
		} catch (NoSuchFieldException nsfe) {
			throw new RuntimeException(nsfe);
		}
		Type listType = field.getGenericType();
		System.out.println("generic field: " + listType);

		Method method;
		try {
			method = AndroidTest8019.class.getMethod("fancyMethod",
				new Class[] { ArrayList.class });
		} catch (NoSuchMethodException nsme) {
			throw new RuntimeException(nsme);
		}
		Type[] parmTypes = method.getGenericParameterTypes();
		Type ret = method.getGenericReturnType();
		System.out.println("generic method " + method.getName() + " params='"
			+ stringifyTypeArray(parmTypes) + "' ret='" + ret + "'");

		Constructor ctor;
		try {
			ctor = AndroidTest8019.class.getConstructor(new Class[] { ArrayList.class });
		} catch (NoSuchMethodException nsme) {
			throw new RuntimeException(nsme);
		}
		parmTypes = ctor.getGenericParameterTypes();
		System.out.println("generic ctor " + ctor.getName() + " params='"
			+ stringifyTypeArray(parmTypes) + "'");
	}

	/*
     * Convert an array of Type into a string.  Start with an array count.
     */
	private static String stringifyTypeArray(Type[] types) {
		StringBuilder stb = new StringBuilder();
		boolean first = true;

		stb.append("[" + types.length + "]");

		for (Type t: types) {
			if (first) {
				stb.append(" ");
				first = false;
			} else {
				stb.append(", ");
			}
			stb.append(t.toString());
		}

		return stb.toString();
	}


	public static void main(String[] args) {
		AndroidTest8019 test = new AndroidTest8019();
		test.run();

		checkAccess();
		checkType();
		checkInit();
		checkGeneric();
	}
}


class SuperTarget {
	public SuperTarget() {
		System.out.println("SuperTarget constructor ()V");
		superInt = 1010101;
		superClassInt = 1010102;
	}

	public int myMethod(float floatArg) {
		System.out.println("myMethod (F)I " + floatArg);
		return 6;
	}

	public int superInt;
	public static int superClassInt;
}

class Target extends SuperTarget {
	public Target() {
		System.out.println("Target constructor ()V");
	}

	public Target(int ii, float ff) {
		System.out.println("Target constructor (IF)V : ii="
			+ ii + " ff=" + ff);
		anInt = ii;
	}

	public int myMethod(int intarg) throws NullPointerException, IOException {
		System.out.println("myMethod (I)I");
		System.out.println(" arg=" + intarg + " anInt=" + anInt);
		return 5;
	}

	public int myMethod(String[] strarg, float f, char c) {
		System.out.println("myMethod: " + strarg[0] + " " + f + " " + c + " !");
		return 7;
	}

	public static void myNoargMethod() {
		System.out.println("myNoargMethod ()V");
	}

	public void throwingMethod() {
		System.out.println("throwingMethod");
		throw new NullPointerException("gratuitous throw!");
	}

	public void misc() {
		System.out.println("misc");
	}

	public int anInt;
	public String string1 = "hey";
	public String string2 = "yo";
	public String string3 = "there";
	private String string4 = "naughty";
	public static final String constantString = "a constant string";
	private int aPrivateInt;

	public final int cantTouchThis = 77;

	public long pubLong = 0x1122334455667788L;

	public static double staticDouble = 3.3;
}

class NoisyInit {
	static {
		System.out.println("NoisyInit is initializing");
		//Throwable th = new Throwable();
		//th.printStackTrace();
	}
}

class NoisyInitUser {
	static {
		System.out.println("NoisyInitUser is initializing");
	}
	public void createNoisyInit(NoisyInit ni) {}
}
